using System;
using System.Data;
using System.Collections.Generic;
using System.Text;
using System.Reflection;
using System.Reflection.Emit;
using System.Configuration;

namespace NBear.Common
{
    /// <summary>
    /// BaseEntityFactory
    /// </summary>
    public abstract class BaseEntityFactory
    {
        protected static object syncObj = new object();
        protected static AssemblyBuilder assBuilder = null;
        protected static ModuleBuilder modBuilder = null;
        protected static Dictionary<Type, Type> customEntities = new Dictionary<Type, Type>();

        public const string DYNAMIC_ENTITY_NAMESPACE = "NBear.Common.DynamicEntity";

        private static ResolveEventHandler _ResolveEventHandler = null;

        private static Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
        {
            return assBuilder;
        }

        static BaseEntityFactory()
        {
            if (assBuilder == null)
            {
                AssemblyName assName = new AssemblyName();
                assName.Name = DYNAMIC_ENTITY_NAMESPACE;
                //assName.Name = Guid.NewGuid().ToString("N");
                assBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(assName, AssemblyBuilderAccess.Run);

                //add dynamic assembly to current appdomain
                if (_ResolveEventHandler == null)
                {
                    _ResolveEventHandler = new ResolveEventHandler(CurrentDomain_AssemblyResolve);
                    AppDomain.CurrentDomain.AssemblyResolve += _ResolveEventHandler;
                }
            }

            if (modBuilder == null)
            {
                modBuilder = assBuilder.DefineDynamicModule(assBuilder.GetName().Name, true);
            }
        }

        public static Type GetDynamicEntityType(string entityInterfaceName)
        {
            Check.Require(entityInterfaceName != null, "Argumenets error.", new ArgumentNullException("entityInterfaceName"));

            Type t = null;

            if (assBuilder != null)
            {
                t = assBuilder.GetType(DYNAMIC_ENTITY_NAMESPACE + "." + entityInterfaceName);
            }

            if (customEntities != null && customEntities.ContainsKey(Util.GetType(entityInterfaceName)))
            {
                t = customEntities[Util.GetType(entityInterfaceName)];
            }

            if (t == null)
            {
                t = (Type)typeof(EntityFactory<>).MakeGenericType(Util.GetType(entityInterfaceName)).GetMethod("GetDynamicEntityType", BindingFlags.Instance | BindingFlags.Public | BindingFlags.Static, null, Type.EmptyTypes, null).Invoke(null, null); ;
            }

            return t;
        }

        public static Type GetEntityType(string entityFullName)
        {
            Check.Require(entityFullName != null, "Argumenets error.", new ArgumentNullException("entityFullName"));

            if (assBuilder != null)
            {
                return assBuilder.GetType(entityFullName);
            }

            return null;
        }

        public static void RegisterCustomEntity(Type interfaceType, Type ImplType)
        {
            Check.Require(interfaceType != null, "Argumenets error.", new ArgumentNullException("interfaceType"));
            Check.Require(ImplType != null, "Argumenets error.", new ArgumentNullException("ImplType"));

            lock (customEntities)
            {
                if (customEntities.ContainsKey(interfaceType))
                {
                    customEntities[interfaceType] = ImplType;
                }
                else
                {
                    customEntities.Add(interfaceType, ImplType);
                }
            }
        }

        public static void RegisterEntityImplAssembly(string interfaceAssemblyName, string implAssemblyName, string newNamesapce)
        {
            Check.Require(interfaceAssemblyName != null, "Argumenets error.", new ArgumentNullException("interfaceAssemblyName"));
            Check.Require(implAssemblyName != null, "Argumenets error.", new ArgumentNullException("implAssemblyName"));
            Check.Require(newNamesapce != null, "Argumenets error.", new ArgumentNullException("newNamesapce"));

            Assembly interfaceAss = AppDomain.CurrentDomain.Load(interfaceAssemblyName);
            Assembly implAss = AppDomain.CurrentDomain.Load(implAssemblyName);

            lock (customEntities)
            {
                foreach (Type type in interfaceAss.GetTypes())
                {
                    if (type.IsInterface && typeof(IEntity).IsAssignableFrom(type))
                    {
                        Type implType = implAss.GetType(newNamesapce + "." + type.Name);
                        if (implType != null && type.IsAssignableFrom(implType))
                        {
                            RegisterCustomEntity(type, implType);
                        }
                    }
                }
            }
        }

        public static void UnregisterEntityImplAssembly(string interfaceAssemblyName)
        {
            Check.Require(interfaceAssemblyName != null, "Argumenets error.", new ArgumentNullException("interfaceAssemblyName"));

            Assembly interfaceAss = AppDomain.CurrentDomain.Load(interfaceAssemblyName);

            foreach (Type type in interfaceAss.GetTypes())
            {
                if (type.IsInterface && typeof(IEntity).IsAssignableFrom(type))
                {
                    UnregisterCustomEntity(type);
                }
            }
        }

        public static void UnregisterCustomEntity(Type interfaceType)
        {
            Check.Require(interfaceType != null, "Argumenets error.", new ArgumentNullException("interfaceType"));

            lock (customEntities)
            {
                if (customEntities.ContainsKey(interfaceType))
                {
                    customEntities.Remove(interfaceType);
                }
            }
        }

        public static void PreGenerateAllEntities()
        {
            Assembly[] asses = AppDomain.CurrentDomain.GetAssemblies();
            try
            {
                foreach (Assembly ass in asses)
                {
                    if (((!ass.GetName().Name.ToLower().StartsWith("nbear.")) && (!ass.GetName().Name.ToLower().StartsWith("system."))) || ass.GetName().Name.ToLower().StartsWith("nbear.test."))
                    {
                        try
                        {
                            foreach (Type type in ass.GetTypes())
                            {
                                if (type.IsInterface && typeof(IEntity).IsAssignableFrom(type) && typeof(IEntity) != type)
                                {
                                    typeof(EntityFactory<>).MakeGenericType(type).GetMethod("CreateObject", BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public).Invoke(null, null);
                                }
                            }
                        }
                        catch
                        {
                        }
                    }
                }
            }
            catch
            {
            }
        }
    }

    /// <summary>
    /// EntityFactory
    /// </summary>
    /// <typeparam name="IEntityType">IEntityType</typeparam>
    public class EntityFactory<IEntityType> : BaseEntityFactory
        where IEntityType : IEntity
    {
        #region Protected Members

        protected static TypeBuilder typeBuilder = null;

        /// <summary>
        /// Initializes a new instance of the <see cref="T:EntityFactory&lt;IEntityType&gt;"/> class.
        /// </summary>
        protected EntityFactory()
        {
        }

        #endregion

        #region Private Members

        /// <summary>
        /// Emits the load int32 value.
        /// </summary>
        /// <param name="methodIL">The method IL.</param>
        /// <param name="int32Value">The int32 value.</param>
        private static void EmitLoadInt32Value(ILGenerator methodIL, int int32Value)
        {
            Check.Require(methodIL != null);

            if (int32Value == 0)
            {
                methodIL.Emit(OpCodes.Ldc_I4_0);
            }
            else if (int32Value == 1)
            {
                methodIL.Emit(OpCodes.Ldc_I4_1);
            }
            else if (int32Value == 2)
            {
                methodIL.Emit(OpCodes.Ldc_I4_2);
            }
            else if (int32Value == 3)
            {
                methodIL.Emit(OpCodes.Ldc_I4_3);
            }
            else if (int32Value == 4)
            {
                methodIL.Emit(OpCodes.Ldc_I4_4);
            }
            else if (int32Value == 5)
            {
                methodIL.Emit(OpCodes.Ldc_I4_5);
            }
            else if (int32Value == 6)
            {
                methodIL.Emit(OpCodes.Ldc_I4_6);
            }
            else if (int32Value == 7)
            {
                methodIL.Emit(OpCodes.Ldc_I4_7);
            }
            else if (int32Value == 8)
            {
                methodIL.Emit(OpCodes.Ldc_I4_8);
            }
            else
            {
                methodIL.Emit(OpCodes.Ldc_I4_S, int32Value);
            }
        }

        private static void EmitDynamicEntityType()
        {
            typeBuilder = modBuilder.DefineType(DYNAMIC_ENTITY_NAMESPACE + "." + typeof(IEntityType).FullName, TypeAttributes.Public);
            typeBuilder.AddInterfaceImplementation(typeof(IEntityType));
            Type keyValuesType = typeof(KeyValueCollection);
            Type baseType = typeof(Entity<IEntityType>);
            typeBuilder.SetParent(baseType);
            PropertyInfo[] pis = Util.DeepGetProperties(typeof(IEntityType));

            EmitEntitySerializableAttribute();

            EmitEntityGetKeyValuesMethod(baseType);

            EmitEntityUpdateOriginalKeyValuesMethod(baseType);

            EmitEntityDefaultConstructor(baseType);

            EmitEntityProperties(keyValuesType, baseType, pis);
        }

        private static void EmitEntitySerializableAttribute()
        {
            //define serializable attribute
            typeBuilder.SetCustomAttribute(new CustomAttributeBuilder(typeof(SerializableAttribute).GetConstructor(Type.EmptyTypes), new object[0]));
        }

        public static EntityConfiguration GetEntityConfiguration()
        {
            object section = ConfigurationManager.GetSection("EntityConfiguration");

            if (section == null)
            {
                return null;
            }

            if (typeof(System.Xml.XmlNodeList).IsAssignableFrom(section.GetType()))
            {
                section = new EntityConfigurationSection().Create(null, null, ((System.Xml.XmlNodeList)section)[0]);
            }

            if (section.GetType() == typeof(EntityConfigurationCollection))
            {
                return ((EntityConfigurationCollection)section)[typeof(IEntityType).FullName];
            }

            return null;
        }

        private static void EmitEntityProperties(Type keyValuesType, Type baseType, PropertyInfo[] pis)
        {
            //define properties
            int i = 0;
            foreach (PropertyInfo pi in pis)
            {
                PropertyBuilder propBuilder = typeBuilder.DefineProperty(pi.Name, System.Reflection.PropertyAttributes.HasDefault, pi.PropertyType, null);
                MethodAttributes getSetAttr = MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.HideBySig;

                CustomPropertyAttribute c = (GetEntityConfiguration() == null ? null : GetEntityConfiguration().GetCustomProperty(pi.Name));

                object[] customPropertyAttrs = (c == null ? pi.GetCustomAttributes(typeof(CustomPropertyAttribute), false) : new object[] { c });

                if (customPropertyAttrs != null && customPropertyAttrs.Length > 0)
                {
                    //define custom property
                    CustomPropertyAttribute cpa = (CustomPropertyAttribute)customPropertyAttrs[0];
                    
                    MethodBuilder getPropMethodBuilder = typeBuilder.DefineMethod("get_" + pi.Name, pi.GetGetMethod().Attributes & (~MethodAttributes.Abstract) | getSetAttr, pi.PropertyType, Type.EmptyTypes);
                    typeBuilder.DefineMethodOverride(getPropMethodBuilder, pi.GetGetMethod());
                    ILGenerator getPropMethodIL = getPropMethodBuilder.GetILGenerator();
                    getPropMethodIL.DeclareLocal(typeof(string[]));

                    //load keyValues field
                    getPropMethodIL.Emit(OpCodes.Ldarg_0);
                    getPropMethodIL.Emit(OpCodes.Ldfld, baseType.GetField("keyValues", BindingFlags.Instance | BindingFlags.NonPublic));

                    if (cpa.Parameters != null && cpa.Parameters.Length > 0)
                    {
                        //create parms string array
                        EmitLoadInt32Value(getPropMethodIL, cpa.Parameters.Length);
                        getPropMethodIL.Emit(OpCodes.Newarr, typeof(string));
                        getPropMethodIL.Emit(OpCodes.Stloc_0);

                        int j = 0;
                        foreach (string p in cpa.Parameters)
                        {
                            getPropMethodIL.Emit(OpCodes.Ldloc_0);
                            EmitLoadInt32Value(getPropMethodIL, j);
                            getPropMethodIL.Emit(OpCodes.Ldstr, p);
                            getPropMethodIL.Emit(OpCodes.Stelem_Ref);

                            j++;
                        }
                    }
                    else
                    {
                        //create an empty parms string array
                        EmitLoadInt32Value(getPropMethodIL, 0);
                        getPropMethodIL.Emit(OpCodes.Newarr, typeof(string));
                        getPropMethodIL.Emit(OpCodes.Stloc_0);
                    }

                    getPropMethodIL.Emit(OpCodes.Ldloc_0);
                    getPropMethodIL.Emit(OpCodes.Newobj, pi.PropertyType.GetConstructor(new Type[] { typeof(KeyValueCollection), typeof(string[]) }));
                    getPropMethodIL.Emit(OpCodes.Ret);
                    propBuilder.SetGetMethod(getPropMethodBuilder);
                }
                else
                {
                    //define standard property
                    if (pi.CanRead)
                    {
                        //define getMethod
                        MethodBuilder getPropMethodBuilder = typeBuilder.DefineMethod("get_" + pi.Name, pi.GetGetMethod().Attributes & (~MethodAttributes.Abstract) | getSetAttr, pi.PropertyType, Type.EmptyTypes);
                        typeBuilder.DefineMethodOverride(getPropMethodBuilder, pi.GetGetMethod());
                        ILGenerator getPropMethodIL = getPropMethodBuilder.GetILGenerator();
                        getPropMethodIL.Emit(OpCodes.Ldarg_0);
                        getPropMethodIL.Emit(OpCodes.Ldfld, baseType.GetField("keyValues", BindingFlags.Instance | BindingFlags.NonPublic));
                        EmitLoadInt32Value(getPropMethodIL, i);
                        getPropMethodIL.Emit(OpCodes.Callvirt, keyValuesType.GetMethod("get_Item", new Type[] { typeof(int) }));
                        if (pi.PropertyType.IsValueType)
                        {
                            getPropMethodIL.Emit(OpCodes.Unbox_Any, pi.PropertyType);
                        }
                        else
                        {
                            getPropMethodIL.Emit(OpCodes.Castclass, pi.PropertyType);
                        }
                        getPropMethodIL.Emit(OpCodes.Ret);
                        propBuilder.SetGetMethod(getPropMethodBuilder);
                    }

                    if (pi.CanWrite)
                    {
                        //define setMethod
                        MethodBuilder setPropMethodBuilder = typeBuilder.DefineMethod("set_" + pi.PropertyType, pi.GetSetMethod().Attributes & (~MethodAttributes.Abstract) | getSetAttr, null, new Type[] { pi.PropertyType });
                        typeBuilder.DefineMethodOverride(setPropMethodBuilder, pi.GetSetMethod());
                        ILGenerator setPropMethodIL = setPropMethodBuilder.GetILGenerator();
                        setPropMethodIL.Emit(OpCodes.Ldarg_0);
                        setPropMethodIL.Emit(OpCodes.Callvirt, baseType.GetMethod("CheckOriginalKeyValues", BindingFlags.Instance | BindingFlags.NonPublic));
                        setPropMethodIL.Emit(OpCodes.Ldarg_0);
                        setPropMethodIL.Emit(OpCodes.Ldfld, baseType.GetField("keyValues", BindingFlags.Instance | BindingFlags.NonPublic));
                        EmitLoadInt32Value(setPropMethodIL, i);
                        setPropMethodIL.Emit(OpCodes.Ldarg_1);
                        if (pi.PropertyType.IsValueType)
                        {
                            setPropMethodIL.Emit(OpCodes.Box, pi.PropertyType);
                        }
                        setPropMethodIL.Emit(OpCodes.Callvirt, keyValuesType.GetMethod("set_Item", new Type[] { typeof(int), typeof(object) }));
                        setPropMethodIL.Emit(OpCodes.Ret);
                        propBuilder.SetSetMethod(setPropMethodBuilder);
                    }

                    i++;
                }
            }
        }

        private static void EmitEntityDefaultConstructor(Type baseType)
        {
            //define default constructor
            ConstructorBuilder consBuilder = typeBuilder.DefineConstructor(MethodAttributes.Public, CallingConventions.Standard, Type.EmptyTypes);
            ILGenerator ctorIL = consBuilder.GetILGenerator();
            ctorIL.Emit(OpCodes.Ldarg_0);
            ctorIL.Emit(OpCodes.Call, baseType.GetConstructor(new Type[0]));
            ctorIL.Emit(OpCodes.Ret);
        }

        private static void EmitEntityGetKeyValuesMethod(Type baseType)
        {
            //define GetKeyValues()
            MethodInfo mi = typeof(IEntity).GetMethod("GetKeyValues");
            ParameterInfo[] paramInfos = mi.GetParameters();
            int paramlength = paramInfos.Length;
            Type[] paramTypes = new Type[paramlength];
            for (int i = 0; i < paramlength; i++)
            {
                paramTypes[i] = paramInfos[i].ParameterType;
            }
            MethodBuilder getKeyValuesMethodBuilder = typeBuilder.DefineMethod(mi.Name, mi.Attributes & (~MethodAttributes.Abstract) | MethodAttributes.Public, mi.CallingConvention, mi.ReturnType, paramTypes);
            for (int i = 0; i < paramlength; i++)
            {
                ParameterInfo pi = paramInfos[i];
                getKeyValuesMethodBuilder.DefineParameter(i + 1, pi.Attributes, pi.Name);
            }
            typeBuilder.DefineMethodOverride(getKeyValuesMethodBuilder, mi);
            ILGenerator getKeyValueslMethodIL = getKeyValuesMethodBuilder.GetILGenerator();

            getKeyValueslMethodIL.Emit(OpCodes.Ldarg_0);
            getKeyValueslMethodIL.Emit(OpCodes.Ldfld, baseType.GetField("keyValues", BindingFlags.Instance | BindingFlags.NonPublic));
            getKeyValueslMethodIL.Emit(OpCodes.Ret);

            //define GetOriginalKeyValues()
            mi = typeof(IEntity).GetMethod("GetOriginalKeyValues");
            paramInfos = mi.GetParameters();
            paramlength = paramInfos.Length;
            paramTypes = new Type[paramlength];
            for (int i = 0; i < paramlength; i++)
            {
                paramTypes[i] = paramInfos[i].ParameterType;
            }
            getKeyValuesMethodBuilder = typeBuilder.DefineMethod(mi.Name, mi.Attributes & (~MethodAttributes.Abstract) | MethodAttributes.Public, mi.CallingConvention, mi.ReturnType, paramTypes);
            for (int i = 0; i < paramlength; i++)
            {
                ParameterInfo pi = paramInfos[i];
                getKeyValuesMethodBuilder.DefineParameter(i + 1, pi.Attributes, pi.Name);
            }
            typeBuilder.DefineMethodOverride(getKeyValuesMethodBuilder, mi);
            getKeyValueslMethodIL = getKeyValuesMethodBuilder.GetILGenerator();

            getKeyValueslMethodIL.Emit(OpCodes.Ldarg_0);
            getKeyValueslMethodIL.Emit(OpCodes.Callvirt, baseType.GetMethod("GetOriginalKeyValues", BindingFlags.Instance | BindingFlags.NonPublic));
            getKeyValueslMethodIL.Emit(OpCodes.Ret);
        }

        private static void EmitEntityUpdateOriginalKeyValuesMethod(Type baseType)
        {
            //define UpdateOriginalKeyValues()
            MethodInfo mi = typeof(IEntity).GetMethod("UpdateOriginalKeyValues");
            ParameterInfo[] paramInfos = mi.GetParameters();
            int paramlength = paramInfos.Length;
            Type[] paramTypes = new Type[paramlength];
            for (int i = 0; i < paramlength; i++)
            {
                paramTypes[i] = paramInfos[i].ParameterType;
            }
            MethodBuilder updateOriginalKeyValuesMethodBuilder = typeBuilder.DefineMethod(mi.Name, mi.Attributes & (~MethodAttributes.Abstract) | MethodAttributes.Public, mi.CallingConvention, mi.ReturnType, paramTypes);
            for (int i = 0; i < paramlength; i++)
            {
                ParameterInfo pi = paramInfos[i];
                updateOriginalKeyValuesMethodBuilder.DefineParameter(i + 1, pi.Attributes, pi.Name);
            }
            typeBuilder.DefineMethodOverride(updateOriginalKeyValuesMethodBuilder, mi);
            ILGenerator updateOriginalKeyValueslMethodIL = updateOriginalKeyValuesMethodBuilder.GetILGenerator();

            updateOriginalKeyValueslMethodIL.Emit(OpCodes.Ldarg_0);
            updateOriginalKeyValueslMethodIL.Emit(OpCodes.Callvirt, baseType.GetMethod("UpdateOriginalKeyValues", BindingFlags.Instance | BindingFlags.NonPublic));
            updateOriginalKeyValueslMethodIL.Emit(OpCodes.Ret);
        }

        private static IEntityType CreateObjectByColumnIndex(DataRow row)
        {
            IEntityType retObj = EntityFactory<IEntityType>.CreateObject();
            KeyValueCollection keyValues = retObj.GetKeyValues();
            for (int i = 0; i < keyValues.GetKeys().Length; i++)
            {
                object val = row[i];
                if (val != DBNull.Value)
                {
                    keyValues[i] = val;
                }
            }
            return retObj;
        }

        private static IEntityType CreateObjectByColumnIndex(IDataReader reader)
        {
            IEntityType retObj = EntityFactory<IEntityType>.CreateObject();
            KeyValueCollection keyValues = retObj.GetKeyValues();
            for (int i = 0; i < keyValues.GetKeys().Length; i++)
            {
                object val = reader.GetValue(i);
                if (val != DBNull.Value)
                {
                    keyValues[i] = val;
                }
            }
            return retObj;
        }

        private static IEntityType CreateObjectByColumnName(DataRow row)
        {
            IEntityType retObj = EntityFactory<IEntityType>.CreateObject();
            KeyValueCollection keyValues = retObj.GetKeyValues();
            string[] keys = keyValues.GetKeys();
            DataColumnCollection columns =  row.Table.Columns;

            for (int i = 0; i < keys.Length; i++)
            {
                if (columns.Contains(keys[i]))
                {
                    object val = row[keys[i]];
                    if (val != DBNull.Value)
                    {
                        keyValues[i] = val;
                    }
                }
            }

            return retObj;
        }

        private static IEntityType CreateObjectByColumnName(IDataReader reader)
        {
            IEntityType retObj = EntityFactory<IEntityType>.CreateObject();
            KeyValueCollection keyValues = retObj.GetKeyValues();
            string[] keys = keyValues.GetKeys();
            
            for (int i = 0; i < keyValues.GetKeys().Length; i++)
            {
                try
                {
                    object val = reader.GetValue(reader.GetOrdinal(keys[i]));
                    if (val != DBNull.Value)
                    {
                        keyValues[i] = val;
                    }
                }
                catch
                {
                    //if column not in this query, just skip
                }
            }
            return retObj;
        }

        #endregion

        #region Constructors

        #endregion

        #region Static Members

        public static Type GetDynamicEntityType()
        {
            Type t = null;

            if (customEntities.ContainsKey(typeof(IEntityType)))
            {
                t = customEntities[typeof(IEntityType)];
            }

            if (t != null)
            {
                return t;
            }

            try
            {
                t = assBuilder.GetType(DYNAMIC_ENTITY_NAMESPACE + "." + typeof(IEntityType).FullName);
            }
            catch
            {
            }

            if (t != null)
            {
                return t;
            }
            else
            {
                lock (syncObj)
                {
                    //create dynamic IEntityType Assembly & Type through Emit
                    if (typeBuilder == null)
                    {
                        EmitDynamicEntityType();
                    }
                }

                return typeBuilder.CreateType();
            }
        }

        /// <summary>
        /// Creates the object.
        /// </summary>
        /// <returns></returns>
        public static IEntityType CreateObject()
        {
            IEntityType obj = (IEntityType)Activator.CreateInstance(GetDynamicEntityType());
            return obj;
        }

        public static IEntityType CreateObject(KeyValueCollection keyValues)
        {
            if (keyValues == null)
            {
                return default(IEntityType);
            }

            IEntityType obj = CreateObject();

            KeyValueCollection targetKeyValues = obj.GetKeyValues();
            for (int i = 0; i < keyValues.GetKeys().Length; i++)
            {
                targetKeyValues[i] = keyValues[i];
            }

            return obj;
        }

        /// <summary>
        /// Creates the object.
        /// </summary>
        /// <param name="row">The row.</param>
        /// <returns></returns>
        public static IEntityType CreateObject(DataRow row, bool readColumnByIndex)
        {
            Check.Require(row != null, "Arguments error.", new ArgumentNullException("row"));

            return readColumnByIndex ? CreateObjectByColumnIndex(row) : CreateObjectByColumnName(row);
        }

        /// <summary>
        /// Creates the object.
        /// </summary>
        /// <param name="reader">The reader.</param>
        /// <returns></returns>
        public static IEntityType CreateObject(IDataReader reader, bool readColumnByIndex)
        {
            Check.Require(reader != null, "Arguments error.", new ArgumentNullException("reader"));

            return readColumnByIndex ? CreateObjectByColumnIndex(reader) : CreateObjectByColumnName(reader);
        }

        public static IEntityType CreateObject(IDataReader reader)
        {
            Check.Require(reader != null, "Arguments error.", new ArgumentNullException("reader"));

            return CreateObjectByColumnName(reader);
        }

        public static IEntityType CreateObject(DataRow row)
        {
            Check.Require(row != null, "Arguments error.", new ArgumentNullException("row"));

            return CreateObjectByColumnName(row);
        }

        /// <summary>
        /// Creates the object list.
        /// </summary>
        /// <param name="table">The table.</param>
        /// <returns></returns>
        public static IEntityType[] CreateObjectList(DataTable table, bool readColumnByIndex)
        {
            if (table != null && table.Rows.Count > 0)
            {
                IEntityType[] objs = new IEntityType[table.Rows.Count];
                for (int i = 0; i < table.Rows.Count; i++)
                {
                    objs[i] = CreateObject(table.Rows[i], readColumnByIndex);
                }
                return objs;
            }

            return null;
        }

        public static IEntityType[] CreateObjectList(DataTable table)
        {
            return CreateObjectList(table, false);
        }

        public static IEntityType[] CreateObjectList(IDataReader reader)
        {
            return CreateObjectList(reader, false);
        }

        /// <summary>
        /// Creates the object list.
        /// </summary>
        /// <param name="reader">The reader.</param>
        /// <returns></returns>
        public static IEntityType[] CreateObjectList(IDataReader reader, bool readColumnByIndex)
        {
            if (reader == null)
            {
                return null;
            }

            List<IEntityType> objs = new List<IEntityType>();
            while (reader.Read())
            {
                objs.Add(CreateObject(reader, readColumnByIndex));
            }
            return objs.ToArray();
        }

        public static DataTable EntityToDataTable(IEntityType obj)
        {
            Check.Require(obj != null, "Arguments error.", new ArgumentNullException("obj"));

            DataTable table = new DataTable(typeof(IEntityType).Name);
            DataRow row = table.NewRow();

            KeyValueCollection keyValues = obj.GetKeyValues();
            string[] keys = keyValues.GetKeys();
            Type[] types = keyValues.GetTypes();
            for (int i = 0; i < keys.Length; i++)
            {
                DataColumn column = new DataColumn(keys[i], types[i]);
                table.Columns.Add(column);
                row[i] = (keyValues[i] == null ? DBNull.Value : keyValues[i]);
            }

            table.Rows.Add(row);

            table.AcceptChanges();// edit by alex
            return table;
        }

        public static DataTable EntityArrayToDataTable(IEntityType[] objs)
        {
            Check.Require(objs != null, "Arguments error.", new ArgumentNullException("objs"));

            DataTable table = new DataTable(typeof(IEntityType).Name);
            KeyValueCollection keyValues = CreateObject().GetKeyValues();
            string[] keys = keyValues.GetKeys();
            Type[] types = keyValues.GetTypes();
            for (int i = 0; i < keys.Length; i++)
            {
                DataColumn column = new DataColumn(keys[i], types[i]);
                table.Columns.Add(column);
            }

            if (objs == null)
            {
                return table;
            }

            for (int i = 0; i < objs.Length; i++)
            {
                keyValues = objs[i].GetKeyValues();// edit by alex
                DataRow row = table.NewRow();

                for (int j = 0; j < keys.Length; j++)
                {
                    row[j] = (keyValues[j] == null ? DBNull.Value : keyValues[j]);
                }

                table.Rows.Add(row);
            }

            table.AcceptChanges();// edit by alex
            return table;
        }

        public static ReturnType ConvertToEntity<ReturnType>(IEntityType obj)
            where ReturnType : IEntity
        {
            Check.Require(obj != null, "Argumenets error.", new ArgumentNullException("obj"));

            if (typeof(ReturnType).IsAssignableFrom(typeof(IEntityType)))
            {
                return (ReturnType)Convert.ChangeType(obj, typeof(ReturnType));
            }

            string[] columns = obj.GetKeyValues().GetKeys();
            object[] values = obj.GetKeyValues().GetValues();

            ReturnType retObj;
            if (typeof(ReturnType).IsInterface)
            {
                retObj = EntityFactory<ReturnType>.CreateObject();
            }
            else
            {
                retObj = Activator.CreateInstance<ReturnType>();
            }
            KeyValueCollection keyValues = retObj.GetKeyValues();
            for (int i = 0; i < columns.Length; i++)
            {
                keyValues[columns[i]] = values[i];
            }

            return retObj;
        }

        public static ReturnType[] ConvertToEntityArray<ReturnType>(IEntityType[] objs)
            where ReturnType : IEntity
        {
            Check.Require(objs != null, "Argumenets error.", new ArgumentNullException("objs"));

            if (typeof(ReturnType[]).IsAssignableFrom(typeof(IEntityType[])))
            {
                return (ReturnType[])Convert.ChangeType(objs, typeof(ReturnType[]));
            }

            ReturnType[] retArr = null;

            if (objs != null && objs.Length > 0)
            {
                retArr = new ReturnType[objs.Length];
                for (int i = 0; i < retArr.Length; i++)
                {
                    retArr[i] = ConvertToEntity<ReturnType>(objs[i]);
                }
            }

            return retArr;
        }

        public static IEntityType EntityClone(IEntityType obj)
        {
            Check.Require(obj != null, "Arguments error.", new ArgumentNullException("obj"));

            object[] values = obj.GetKeyValues().GetValues();

            IEntityType retObj = EntityFactory<IEntityType>.CreateObject();
            KeyValueCollection keyValues = retObj.GetKeyValues();
            for (int i = 0; i < values.Length; i++)
            {
                keyValues[i] = values[i];
            }

            return retObj;
        }

        /// <summary>
        /// compare two entities and return writable column names with defferent or same values
        /// </summary>
        /// <returns>column names with defferent values</returns>
        public static string[] CompareEntities(IEntityType left, IEntityType right, bool includePrimaryKeys, bool returnEqualColumns, bool includeEqualColumnWithDefaultValue)
        {
            Check.Require(left != null, "Argumenets error.", new ArgumentNullException("left"));
            Check.Require(right != null, "Argumenets error.", new ArgumentNullException("right"));

            string[] columns;

            if (includePrimaryKeys)
            {
                columns = Entity<IEntityType>.GetInsertColumnNames();
            }
            else
            {
                columns = Entity<IEntityType>.GetUpdatableColumnNames();
            }

            object[] leftValues = left.GetKeyValues().GetValues(columns);
            object[] rightValues = right.GetKeyValues().GetValues(columns);

            List<string> list = new List<string>();
            for (int i = 0; i < leftValues.Length; i++)
            {
                if (returnEqualColumns)
                {
                    if (leftValues[i] == rightValues[i])
                    {
                        if (includeEqualColumnWithDefaultValue)
                        {
                            list.Add(columns[i]);
                        }
                        else
                        {
                            object val = leftValues[i];

                            if ((val != null && (!val.GetType().IsValueType)) || (val != null && val.GetType().IsValueType && val.ToString() != Util.DefaultValue(val.GetType()).ToString()))
                            {
                                list.Add(columns[i]);
                            }
                        }
                    }
                }
                else
                {
                    if (leftValues[i] != rightValues[i])
                    {
                        list.Add(columns[i]);
                    }
                }
            }

            return list.ToArray();
        }

        #endregion
    }
}
